package iec104_client

import (
	"context"
	"errors"
	"github.com/sirupsen/logrus"
	iec104_apdu "go-iot-gateway/pkg/iec104/apdu"
	"net"
	"sync"
	"time"
)

type Iec104Client struct {
	options ClientOptions
	//连接器
	conn net.Conn
	//写通道
	writeChan chan []byte
	//读通道
	readChan chan *iec104_apdu.APDU

	iFrameNum int
	rsn       int16
	ssn       int16

	lock sync.Mutex

	//客户端上下文
	context            context.Context
	contextCancelFunc  context.CancelFunc
	goroutineWaitGroup *sync.WaitGroup
	//onConnectionLost ConnectionLostHandler
	//onReconnecting   ReconnectHandler
	//onConnect        OnConnectHandler
}

// todo iec104 规约配置参数
func NewClient(options *ClientOptions) *Iec104Client {
	if options.Logger != nil {
		iec104_apdu.SetLogger(options.Logger)
	}

	return &Iec104Client{
		options:            *options,
		goroutineWaitGroup: new(sync.WaitGroup),
	}
}

// 写数据
func (client *Iec104Client) SendMsg(bytes []byte) error {

	if len(client.writeChan) == client.options.WriteChanSize {
		//要用指定错误类型
		return errors.New("写队列溢出")
	}

	client.writeChan <- bytes
	return nil
}

func (c *Iec104Client) SendUFrame(cmd [4]byte) {
	//c.options.Logger.Debugf("发送U帧")
	data := iec104_apdu.ConvertIec104Bytes(iec104_apdu.Convert4BytesToSlice(cmd))
	c.writeChan <- data
}

// sendTotalCall 发送总召唤
func (c *Iec104Client) SendTotalCall() {
	ssnBytes := iec104_apdu.ParseLittleEndianUInt16(uint16(c.ssn << 1))
	rsnBytes := iec104_apdu.ParseLittleEndianUInt16(uint16(c.rsn << 1))
	totalCallData := make([]byte, 0, 0)
	totalCallData = append(totalCallData, ssnBytes...)
	totalCallData = append(totalCallData, rsnBytes...)
	totalCallData = append(totalCallData, 0x64, 0x01, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14)
	data := iec104_apdu.ConvertIec104Bytes(totalCallData)
	//c.options.Logger.Debugf("发送总召唤")
	c.writeChan <- data
}

// sendSFrame 发送S帧
func (c *Iec104Client) SendSFrame() {
	rsnBytes := iec104_apdu.ParseLittleEndianUInt16(uint16(c.rsn << 1))
	sendBytes := make([]byte, 0, 0)
	sendBytes = append(sendBytes, 0x01, 0x00)
	sendBytes = append(sendBytes, rsnBytes...)
	data := iec104_apdu.ConvertIec104Bytes(sendBytes)
	//c.options.Logger.Debugf("发送S帧")
	c.writeChan <- data
}

// sendTotalCall 发送电度总召唤
func (c *Iec104Client) SendElectricityTotalCall() {
	ssnBytes := iec104_apdu.ParseLittleEndianUInt16(uint16(c.ssn << 1))
	rsnBytes := iec104_apdu.ParseLittleEndianUInt16(uint16(c.rsn << 1))
	totalCallData := make([]byte, 0, 0)
	totalCallData = append(totalCallData, ssnBytes...)
	totalCallData = append(totalCallData, rsnBytes...)
	totalCallData = append(totalCallData, 0x65, 0x01, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05)
	data := iec104_apdu.ConvertIec104Bytes(totalCallData)
	//c.options.Logger.Debugf("发送电度总召唤")
	c.writeChan <- data
}

// 连接
func (client *Iec104Client) Connect() {
	if client.context != nil {
		return
	}

	client.lock.Lock()
	defer client.lock.Unlock()
	//已经创建了连接
	if client.context != nil {
		return
	}

	var err error
	client.options.Logger.Infof("开始连接服务器:%v", client.options.Address)

	client.context, client.contextCancelFunc = context.WithCancel(context.Background())
	client.rsn = 0
	client.ssn = 0
	client.iFrameNum = 0
	retryTimes := 0

	for {
		client.conn, err = net.DialTimeout("tcp", client.options.Address, client.options.ConnectTimeout)

		//连接成功
		if err == nil {
			client.readChan = make(chan *iec104_apdu.APDU, client.options.ReadChanSize)
			client.writeChan = make(chan []byte, client.options.WriteChanSize)
			go readHandler(client)
			go writeHandler(client)
			go client.keepalive()
			go client.options.OnConnect()
			//启动激活帧
			client.SendUFrame(iec104_apdu.UFrameFunctionStartDTA)
			break
		}

		if !client.options.ConnectRetry {
			//不需要重连
			break
		}
		retryTimes++
		client.options.Logger.Debug("iec-client 连接重试 ", retryTimes)
		time.Sleep(client.options.ConnectRetryInterval)

		//todo 主备切换
		//i++
		//if i == retryTimes && c.subAddress != "" {
		//	i = 0
		//	if c.curAddress == c.address {
		//		c.curAddress = c.subAddress
		//	} else {
		//		c.curAddress = c.address
		//	}
		//	c.Logger.Infof("尝试超过3次，切换服务器为:%s,开始第%d次重试", c.curAddress, i+1)
		//} else {
		//	c.Logger.Infof("连接服务器失败，开始第%d次重试", i+1)
		//}

	}

}

// 断连
func (client *Iec104Client) DisConnect() <-chan time.Duration {
	client.lock.Lock()
	defer client.lock.Unlock()

	client.context = nil

	now := time.Now()
	client.contextCancelFunc()
	client.releaseRes()

	done := make(chan time.Duration)

	go func() {
		client.goroutineWaitGroup.Wait()
		done <- time.Since(now)
	}()

	return done
}

// incrRsn 增加rsn
func (c *Iec104Client) incrRsn() {
	c.lock.Lock()
	defer func() {
		c.lock.Unlock()
	}()

	c.rsn++
	if c.rsn < 0 {
		c.rsn = 0
	}

}

// 释放资源
func (client *Iec104Client) releaseRes() {
	if client.conn != nil {
		err := client.conn.Close()
		if err != nil {
			logrus.Info("iec104 客户端关闭失败")
		}
	}
	close(client.readChan)
	close(client.writeChan)
}

// 保活
func (client *Iec104Client) keepalive() {
	defer func() {
		logrus.Error("iec 保活协程死了!!!!")
	}()

	for {
		time.Sleep(1 * time.Second)
		select {
		case <-client.context.Done():

			//通过调用 DisConnect ，不进行保活
			if client.context == nil {
				return
			}

			go client.options.OnConnectionLost()

			client.releaseRes()
			client.context = nil

			//建立连接
			logrus.Warn("连接中断,重新连接")
			client.Connect()
			return
		}
	}

}
